////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001-2006.
// -------------------------------------------------------------------------
//  File name:   SubObjectSelectionReferenceFrameCalculator.cpp
//  Version:     v1.00
//  Created:     9/3/2006 Michael Smith
//  Compilers:   Visual Studio.NET 2005
//  Description: Calculate the reference frame for sub-object selections.
// -------------------------------------------------------------------------
//  History:
//	03/03/2010 Be refactored by encapsulating SBrush by Jaesik
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SubObjectSelectioNReferenceFrameCalculator.h"
#include "Brush/Brush.h"

SubObjectSelectionReferenceFrameCalculator::SubObjectSelectionReferenceFrameCalculator(ESubObjElementType selectionType)
:	bAnySelected(false),
pos(0.0f, 0.0f, 0.0f),
normal(0.0f, 0.0f, 0.0f),
nNormals(0),
selectionType(selectionType),
bUseExplicitFrame(false),
bExplicitAnySelected(false)
{
}

void SubObjectSelectionReferenceFrameCalculator::AddBrush(const Matrix34& worldTM, SBrush* pBrush)
{
	if (this->selectionType == SO_ELEM_VERTEX)
	{
		for(size_t i = 0; i < pBrush->GetNumberOfFaces(); ++i)
		{
			if (!pBrush->IsValidFace(i))
				continue;
			for (int j = 0; j < pBrush->GetNumberOfFacePoints(i); j++)
			{	
				if( pBrush->IsPointSelected(i,j) )
				{
					Vec3 worldPosition = worldTM * pBrush->GetFacePointPos(i,j);
					if (std::find(this->positions.begin(),this->positions.end(),worldPosition) == this->positions.end())
					{
						this->positions.push_back(worldPosition);
						// This position is not selected yet.
						this->bAnySelected = true;
						this->normal = this->normal - pBrush->GetFaceNormal(i);
						this->nNormals++;
						this->pos = this->pos + worldPosition;
					}
				}
			}
		}
	}

	if (this->selectionType == SO_ELEM_EDGE)
	{	
		size_t  nNumberOfFaces(pBrush->GetNumberOfFaces());
		size_t	nCurrentFace(0);
		int			nCurrrentEdge(0);
		int			anPolyIndices[2];

		for (nCurrentFace=0;nCurrentFace<nNumberOfFaces;nCurrentFace++)
		{	
			if (!pBrush->IsValidFace(nCurrentFace))
			{
				continue;
			}

			for (nCurrrentEdge=0;nCurrrentEdge<pBrush->GetNumberOfFaceEdges(nCurrentFace);nCurrrentEdge++)
			{	
				if( pBrush->GetFaceSelectedEdge(nCurrentFace)&(1<<nCurrrentEdge) )
				{
					pBrush->MapEdgeIndexToPolyIndices(nCurrentFace,nCurrrentEdge,anPolyIndices[0],anPolyIndices[1]);

					this->bAnySelected = true;

					// Avarages all face normals.
					// OBS: the current algorithm is not a good one to estime the correct normals.
					// Spherical or quaternion interpolation should be used instead of arithmetic avarage.
					this->normal = this->normal + pBrush->GetFaceNormal(nCurrentFace);
					nNormals++;

					// As we just need a position where to place the gizmo, we don't need to refer to
					// both faces in order to find the correct position. We just need to place between any
					// two selected vertices (an edge means 2 vertices).
					this->pos=
						pBrush->GetFacePointPos(nCurrentFace,anPolyIndices[0])
						+
						// This term calculates the vector from the second vertex to the first vertex of
						// the edge. As we want to place the gizmo between them, we must divide it's lenght
						// by two.
						(
						pBrush->GetFacePointPos(nCurrentFace,anPolyIndices[1])
						-
						pBrush->GetFacePointPos(nCurrentFace,anPolyIndices[0])
						)
						/
						2.0f;

					this->pos=worldTM.TransformPoint(this->pos)*nNormals;
				}
			}
		}

		if (nNormals > 0)
		{
			this->normal = this->normal / nNormals;
			if (!this->normal.IsZero())
			{
				this->normal.Normalize();
			}
		}
	}

	if (this->selectionType == SO_ELEM_FACE || this->selectionType == SO_ELEM_POLYGON)
	{
		// Average all face normals.
		for(size_t i = 0; i < pBrush->GetNumberOfFaces(); i++)
		{	
			if (pBrush->IsFaceSelected(i))
			{
				const Matrix34& brushTM = pBrush->GetMatrix();
				this->bAnySelected = true;
				this->normal = this->normal - brushTM.TransformVector(pBrush->GetFaceNormal(i));
				this->nNormals++;
				pBrush->CalculateFaceCenter(i);
				this->pos = this->pos + worldTM * pBrush->GetFaceCenter(i);
			}
		}
	}
}

void SubObjectSelectionReferenceFrameCalculator::SetExplicitFrame(bool bAnySelected, const Matrix34& refFrame)
{
	this->refFrame = refFrame;
	this->bUseExplicitFrame = true;
	this->bExplicitAnySelected = bAnySelected;
}

bool SubObjectSelectionReferenceFrameCalculator::GetFrame(Matrix34& refFrame)
{
	if (this->bUseExplicitFrame)
	{
		refFrame = this->refFrame;
		return this->bExplicitAnySelected;
	}
	else
	{
		refFrame.SetIdentity();

		if (this->nNormals > 0)
		{
			this->normal = this->normal / this->nNormals;
			if (!this->normal.IsZero())
				this->normal.Normalize();

			// Average position.
			this->pos = this->pos / this->nNormals;
			refFrame.SetTranslation(this->pos);
		}

		if (this->bAnySelected)
		{
			if (!this->normal.IsZero())
			{
				Vec3 xAxis(1,0,0),yAxis(0,1,0),zAxis(0,0,1);
				if (this->normal.IsEquivalent(zAxis) || normal.IsEquivalent(-zAxis))
					zAxis = xAxis;
				xAxis = this->normal.Cross(zAxis).GetNormalized();
				yAxis = xAxis.Cross(this->normal).GetNormalized();
				refFrame.SetFromVectors( xAxis,yAxis,normal,pos );
			}
		}

		return bAnySelected;
	}
}
